#include "ThreadSimpleAsyncDemoOfCpp.h"
#include <iostream>
#include <future>
#include <thread>

using namespace std;

void threadSimpleAsyncDemoOfCpp()
{
    cout << "最简多线程异步使用案例（基于 C++ 标准库）开始演示" << endl;
    // C++11 中增加的线程类，使得我们能够非常方便的创建和使用线程，但有时会有些不方便，比如需要获取线程返回的结果，就不能通过 join() 得到结果，只能通过一些额外手段获得，
    // 比如：定义一个全局变量，在子线程中赋值，在主线程中读这个变量的值，整个过程比较繁琐。C++ 提供的线程库中提供了一些类用于访问异步操作的结果。
    //
    // 那么，什么叫做异步呢？
    //
    //                    ┌───> 星巴克 ─────制作咖啡中...─────> 拿铁
    //                    │                                     ↓
    // 顾客 ───> 星巴克────┤                                 通知取咖啡
    //                    │                                     ↓
    //                    └───> 顾客 ──────玩手机等待...──────> 顾客
    //
    // 我们去星巴克买咖啡，因为都是现磨的，所以需要等待，但是我们付完账后不会站在柜台前死等，
    // 而是去找个座位坐下来玩玩手机打发一下时间，当店员把咖啡磨好之后，就会通知我们过去取，这就叫做异步。
    //  * 顾客（主线程）发起一个任务（子线程磨咖啡），磨咖啡的过程中顾客去做别的事情了，有两条时间线（异步）；
    //  * 顾客（主线程）发起一个任务（子线程磨咖啡），磨咖啡的过程中顾客没去做别的事情而是死等，这时就只有一条时间线（同步），此时效率相对较低。
    //
    // 因此多线程程序中的任务大都是异步的，主线程和子线程分别执行不同的任务，
    // 如果想要在主线中得到某个子线程任务函数返回的结果可以使用 C++11 提供的一些辅助类，先来介绍一下这些类：

    // 1. std::future
    // std:future 是一个用于获取线程返回值的类，它能够接受一个来自绑定的 std::promise 返回的数据，这个类需要和其他类或函数搭配使用，为获取线程函数中的某个值提供便利。
    //
    // 类的定义
    // 通过类的定义可以得知，future 是一个模板类，也就是这个类可以存储任意指定类型的数据。
    // 定义于头文件 <future>
    //   template<class T> class future;
    //   template<class T> class future<T&>;
    //   template<>        class future<void>;
    // 
    // 构造函数
    //   构造函数一
    //   future() noexcept;
    //   构造函数二
    //   future(future&& other) noexcept;
    //   构造函数三
    //   future(const future& other) = delete;
    //
    // * 构造函数一：默认无参构造函数
    // * 构造函数二：移动构造函数，转移资源的所有权
    // * 构造函数三：使用 =delete 显示删除拷贝构造函数, 不允许进行对象之间的拷贝
    //
    // 常用成员函数（public）
    // 一般情况下使用 = 进行赋值操作就进行对象的拷贝，但是 future 对象不可用复制，因此会根据实际情况进行处理：
    //  * 如果 other 是右值，那么转移资源的所有权（它实现了转移构造函数）。
    //  * 如果 other 是非右值，不允许进行对象之间的拷贝（该函数被显示删除禁止使用）。
    //   future& operator=(future&& other) noexcept;
    //   future& operator=(const future& other) = delete;
    //
    // 取出 future 对象内部保存的数据，其中 void get() 是为 future<void> 准备的，此时对象内部类型就是 void，该函数是一个阻塞函数，当子线程的数据就绪后解除阻塞就能得到传出的数值了。
    //   T get();
    //   T& get();
    //   void get();
    //
    // 因为 future 对象内部存储的是异步线程任务执行完毕后的结果，是在调用之后的将来得到的，
    // 因此可以通过调用 wait() 方法，阻塞当前线程，等待这个子线程的任务执行完毕，任务执行完毕当前线程的阻塞也就解除了。
    //   void wait() const;
    //
    // 如果当前线程调用 wait() 方法就会死等，直到子线程任务执行完毕将返回值写入到 future 对象中；
    // 调用 wait_for() 只会让线程阻塞一定的时长，但是这样并不能保证对应的那个子线程中的任务已经执行完毕了。
    // 
    // wait_until() 和 wait_for() 函数功能是差不多，前者是阻塞到某一指定的时间点，后者是阻塞一定的时长。
    //   template<class Rep, class Period>
    //   std::future_status wait_for(const std::chrono::duration<Rep, Period>& timeout_duration) const;
    //   
    //   template<class Clock, class Duration>
    //   std::future_status wait_until(const std::chrono::time_point<Clock, Duration>& timeout_time) const;
    // 
    // 当 wait_until() 和 wait_for() 函数返回之后，并不能确定子线程当前的状态，因此我们需要判断函数的返回值，这样就能知道子线程当前的状态了：
    // ──────────────────────────────────────────────────────────────────────────────
    //          常量                         解释
    // future_status::deferred     子线程中的任务函仍未启动
    // future_status::ready        子线程中的任务已经执行完毕，结果已就绪
    // future_status::timeout      子线程中的任务正在执行中，指定等待时长已用完
    // ──────────────────────────────────────────────────────────────────────────────

    // 2. std::promise
    // std::promise 是一个协助线程赋值的类，它能够将数据和 future 对象绑定起来，为获取线程函数中的某个值提供便利。
    // 
    // 类定义
    // 通过 std::promise 类的定义可以得知，这也是一个模板类，我们要在线程中传递什么类型的数据，模板参数就指定为什么类型。
    //   定义于头文件 <future>
    //   template<class R> class promise;
    //   template<class R> class promise<R&>;
    //   template<>        class promise<void>;
    //
    // 构造函数
    //   构造函数一
    //   promise();
    //   构造函数二
    //   promise(promise&& other) noexcept;
    //   构造函数三
    //   promise(const promise& other) = delete;
    // 构造函数一：默认构造函数，得到一个空对象；
    // 构造函数二：移动构造函数；
    // 构造函数三：使用 =delete 显示删除拷贝构造函数, 不允许进行对象之间的拷贝；
    //
    // 公共成员函数
    // 在 std::promise 类内部管理着一个 future 类对象，调用 get_future() 就可以得到这个 future 对象了
    //   std::future<T> get_future();
    //
    // 存储要传出的 value 值，并立即让状态就绪，这样数据被传出其它线程就可以得到这个数据了。重载的第四个函数是为 promise<void> 类型的对象准备的。
    //   void set_value(const R& value);
    //   void set_value(R&& value);
    //   void set_value(R& value);
    //   void set_value();
    //
    // 存储要传出的 value 值，但是不立即令状态就绪。在当前线程退出时，子线程资源被销毁，再令状态就绪。
    //   void set_value_at_thread_exit(const R& value);
    //   void set_value_at_thread_exit(R&& value);
    //   void set_value_at_thread_exit(R& value);
    //   void set_value_at_thread_exit();
 
    // promise 的使用
    // 通过promise传递数据的过程一共分为5步：
    //  1. 在主线程中创建 std::promise 对象
    //  2. 将这个 std::promise 对象通过引用的方式传递给子线程的任务函数
    //  3. 在子线程任务函数中给 std::promise 对象赋值
    //  4. 在主线程中通过 std::promise 对象取出绑定的 future 实例对象
    //  5. 通过得到的 future 对象取出子线程任务函数中返回的值。
    
    // 子线程任务函数执行期间，让状态就绪
    promise<int> pr1;
    thread futureThread1([](promise<int> &pr) {
        pr.set_value(100);
        this_thread::sleep_for(chrono::seconds(2));
        cout << "set_value 睡醒了" << endl;
    }, ref(pr1));
    future<int> f1 = pr1.get_future();
    int value1 = f1.get();
    cout << "获取到 set_value 传递的值：" << value1 << endl;
    futureThread1.join();
    // 示例程序的中子线程的任务函数指定的是一个匿名函数，在这个匿名的任务函数执行期间通过 pr.set_value(100); 传出了数据并且激活了状态，
    // 数据就绪后，外部主线程中的 int value = f.get(); 解除阻塞，并将得到的数据打印出来，2 秒钟之后子线程休眠结束，匿名的任务函数执行完毕。


    // 子线程任务函数执行结束，让状态就绪
    promise<int> pr2;
    thread futureThread2([](promise<int> &pr) {
        pr.set_value_at_thread_exit(101);
        this_thread::sleep_for(chrono::seconds(2));
        cout << "set_value_at_thread_exit 睡醒了" << endl;
    }, ref(pr2));
    future<int> f2 = pr2.get_future();
    int value2 = f2.get();
    cout << "获取到 set_value_at_thread_exit 传递的值：" << value2 << endl;
    futureThread2.join();
    // 在示例程序中，子线程的这个匿名的任务函数中通过 pr.set_value_at_thread_exit(100); 在执行完毕并退出之后才会传出数据并激活状态，
    // 数据就绪后，外部主线程中的 int value = f.get(); 解除阻塞，并将得到的数据打印出来，因此子线程在休眠 2 秒钟之后主线程中才能得到传出的数据。

    // 另外，在这两个实例程序中有一个知识点需要强调，在外部主线程中创建的 promise 对象必须要通过引用的方式传递到子线程的任务函数中，在实例化子线程对象的时候，
    // 如果任务函数的参数是引用类型，那么实参一定要放到 std::ref() 函数中，表示要传递这个实参的引用到任务函数中。如果不使用 std::ref() 函数使用 & 引用会报错。
    // summer: std::ref() 是一个模板类实现的引用，与 & 引用可以实现相同功能，但是其作用和意义及原理不一样。
    //         * & 是取变量地址，本质上传递的还是变量。
    //         * ref 是在模板类里面维护了一个变量指针，这个指针指向了引用变量。通过 ref 会得到一个持有变量指针的包装类。取操作真实的变量，本质上操作一样但是实际上ref并不是原始对象了。
    //         * 另外它们在生命周期等方面也有不同，一个受到原始变量的影响，一个不受。

    // 3. std::packaged_task
    // std::packaged_task 类包装了一个可调用对象包装器类对象（可调用对象包装器包装的是可调用对象，可调用对象都可以作为函数来使用）。
    //
    // 这个类可以将内部包装的函数和 future 类绑定到一起，以便进行后续的异步调用，它和 std::promise 有点类似：
    // std::promise 内部保存一个共享状态的值，而 std::packaged_task 保存的是一个函数。
    //
    // 类的定义
    // 通过类的定义可以看到这也是一个模板类，模板类型和要在线程中传出的数据类型是一致的。
    //   定义于头文件 <future>
    //   template<class> 
    //   class packaged_task;
    //   template<class R, class ...Args>
    //   class packaged_task<R(Args...)>;
    //
    // 构造函数
    //   构造函数一
    //   packaged_task() noexcept;
    //   构造函数二
    //   template<class F>
    //   explicit packaged_task(F&& f);
    //   构造函数三
    //   packaged_task(const packaged_task&) = delete;
    //   构造函数四
    //   packaged_task(packaged_task&& rhs) noexcept;
    //
    // 构造函数一：无参构造，构造一个无任务的空对象
    // 构造函数二：通过一个可调用对象，构造一个任务对象
    // 构造函数三：显示删除，不允许通过拷贝构造函数进行对象的拷贝
    // 构造函数四：移动构造函数
    //
    // 常用公共成员函数
    // 通过调用任务对象内部的 get_future() 方法就可以得到一个 future 对象，基于这个对象就可以得到传出的数据了。
    //   std::future<R> get_future();

    // packaged_task 的使用
    // packaged_task 其实就是对子线程要执行的任务函数进行了包装，和可调用对象包装器的使用方法相同，包装完毕之后直接将包装得到的任务对象传递给线程对象就可以了。
    packaged_task<int(int)> task([](int x) {
        return x += 100;
    });

    thread taskThread1(ref(task), 100);
    future<int> f3 = task.get_future();
    int value3 = f3.get();
    cout << "获取到 packaged_task 的值：" << value3 << endl;
    taskThread1.join();
    // 在上面的示例代码中，通过 packaged_task 类包装了一个匿名函数作为子线程的任务函数，最终的得到的这个任务对象需要通过引用的方式传递到子线程内部，
    // 这样才能在主线程的最后通过任务对象得到 future 对象，再通过这个 future 对象取出子线程通过返回值传递出的数据。


    // 4. std::async
    // std::async 函数比前面提到的 std::promise 和 packaged_task 更高级一些，因为通过这函数可以直接启动一个子线程并在这个子线程中执行对应的任务函数，
    // 异步任务执行完成返回的结果也是存储到一个 future 对象中，当需要获取异步任务的结果时，只需要调用 future 类的 get() 方法即可，如果不关注异步任务的结果，
    // 只是简单地等待任务完成的话，可以调用 future 类的 wait() 或者 wait_for() 方法。该函数的函数原型如下：
    //
    //   定义于头文件 <future>
    //   函数一
    //   template<class Function, class... Args>
    //   std::future<std::result_of_t<std::decay_t<Function>(std::decay_t<Args>...)>> async(Function&& f, Args&&... args);
    // 
    //   函数二
    //   template< class Function, class... Args >
    //   std::future<std::result_of_t<std::decay_t<Function>(std::decay_t<Args>...)>> async(std::launch policy, Function&& f, Args&&... args);
    //
    // 可以看到这是一个模板函数，在C++11中这个函数有两种调用方式：
    // 函数一：直接调用传递到函数体内部的可调用对象，返回一个 future 对象
    // 函数二：通过指定的策略调用传递到函数内部的可调用对象，返回一个 future 对象
    //
    // 函数参数:
    //  * f：可调用对象，这个对象在子线程中被作为任务函数使用
    //  * Args：传递给 f 的参数（实参）
    //  * policy：可调用对象 f 的执行策略:
    // ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
    //        策略                             说明
    // std::launch::async       调用 async 函数时创建新的线程执行任务函数
    // std::launch::deferred    调用 async 函数时不执行任务函数，直到调用了 future 的 get() 或者 wait() 时才执行任务（这种方式不会创建新的线程）
    // ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
    
    // 关于std::async()函数的使用，对应的示例代码如下：

    // 方式1
    // 调用async()函数直接创建线程执行任务
    
    cout << "主线程ID: " << this_thread::get_id() << endl;
    // 调用函数直接创建线程执行任务
    future<int> f4 = async([](int x) {
        cout << "子线程ID：" << this_thread::get_id() << endl;
        this_thread::sleep_for(chrono::seconds(2));
        return x + 100;
    }, 100);

    future_status status;
    do {
        status = f4.wait_for(chrono::seconds(1));
        if(status == future_status::deferred)
        {
            cout << "线程还没有执行..." << endl;
            f4.wait();
        }
        else if(status == future_status::ready)
        {
            cout << "子线程返回值: " << f4.get() << endl;
        }
        else if(status == future_status::timeout)
        {
            cout << "任务还未执行完毕, 继续等待..." << endl;
        }
    } while(status != future_status::ready);

    // 调用 async() 函数时不指定策略就是直接创建线程并执行任务，示例代码的主线程中做了如下操作 status = f.wait_for(chrono::seconds(1)); 其实直接调用 f.get() 就能得到子线程的返回值。
    // 这里为了给大家演示 wait_for() 的使用，所以写的复杂了些。



    // 方式2
    // 调用async()函数不创建线程执行任务

    cout << "主线程ID: " << this_thread::get_id() << endl;
    // 调用函数直接创建线程执行任务
    future<int> f5 = async(launch::deferred, [](int x) {
        cout << "子线程ID: " << this_thread::get_id() << endl;
        return x += 100;
    }, 100);

    this_thread::sleep_for(chrono::seconds(2));
    cout << f5.get() << endl;

    // 由于指定了 launch::deferred 策略，因此调用 async() 函数并不会创建新的线程执行任务，
    // 当使用 future 类对象调用了 get() 或者 wait() 方法后才开始执行任务（此处一定要注意调用 wait_for() 函数是不行的）。
    //
    // 通过测试程序输出的结果可以看到，两次输出的线程 ID 是相同的，任务函数是在主线程中被延迟（主线程休眠了 2 秒）调用了。

    // 最终总结：
    //  1. 使用 async() 函数，是多线程操作中最简单的一种方式，不需要自己创建线程对象，并且可以得到子线程函数的返回值。
    //  2. 使用 std::promise 类，在子线程中可以传出返回值也可以传出其他数据，并且可选择在什么时机将数据从子线程中传递出来，使用起来更灵活。
    //  3. 使用 std::packaged_task 类，可以将子线程的任务函数进行包装，并且可以得到子线程的返回值。
}